﻿using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;

namespace Aleator.Core
{
    /// <summary>
    /// Properties and methods determining chord behavior
    /// </summary>
    public class Chord : ICloneable
    {
        #region Members

        private ScaleDegree[] m_majorScaleDegrees = new ScaleDegree[7] { ScaleDegree.I, ScaleDegree.ii, ScaleDegree.iii, ScaleDegree.IV, ScaleDegree.V, ScaleDegree.vi, ScaleDegree.viiDim };
        private ScaleDegree[] m_minorScaleDegrees = new ScaleDegree[7] { ScaleDegree.i, ScaleDegree.iiDim, ScaleDegree.III, ScaleDegree.iv, ScaleDegree.v, ScaleDegree.VI, ScaleDegree.VII };
        private ScaleDegree[] m_harmonicMinorScaleDegrees = new ScaleDegree[7] { ScaleDegree.i, ScaleDegree.iiDim, ScaleDegree.IIIAug, ScaleDegree.iv, ScaleDegree.V, ScaleDegree.VI, ScaleDegree.viiDim };

        //Keep lists of notes at each interval for on-the-fly inversions
        private List<ConcreteNote> m_roots;
        private List<ConcreteNote> m_thirds;
        private List<ConcreteNote> m_fourths;
        private List<ConcreteNote> m_fifths;
        private List<ConcreteNote> m_sixths;
        private List<ConcreteNote> m_sevenths;
        private List<ConcreteNote> m_ninths;

        private ScaleDegree m_degree;
        private ConcreteNote m_root;
        private Token m_token;

        private bool m_isInverted;
        private string m_color;

        #endregion

        #region Constructors

        /// <summary>
        /// Main contstructor
        /// </summary>
        /// <param name="root">Root note for this triad</param>
        /// <param name="interval">The scale degree the root note corresponds to</param>
        /// <param name="inverted">Is the chord eligible for inversion?</param>
        public Chord(ConcreteNote root, int interval, bool inverted)
        {
            int third = interval + 1;
            int fourth = interval + 2;
            int fifth = interval + 3;
            int sixth = interval + 4;
            int seventh = interval + 5;
            int ninth = interval;

            m_root = root;
            m_isInverted = inverted;

            Notes = new List<ConcreteNote>();

            m_roots = new List<ConcreteNote>();
            m_thirds = new List<ConcreteNote>();
            m_fourths = new List<ConcreteNote>();
            m_fifths = new List<ConcreteNote>();
            m_sixths = new List<ConcreteNote>();
            m_sevenths = new List<ConcreteNote>();
            m_ninths = new List<ConcreteNote>();

            //TODO: Account for the top end of the octave
            m_roots.AddRange(CircleOfFifths.Notes.Where(n => n.Interval == interval - 1));

            if (interval > 6)
                m_ninths.AddRange(CircleOfFifths.Notes.Where(n => n.Interval == ninth - 7));
            else
                m_ninths.AddRange(CircleOfFifths.Notes.Where(n => n.Interval == ninth));

            if (interval > 5)
                m_thirds.AddRange(CircleOfFifths.Notes.Where(n => n.Interval == third - 7));
            else
                m_thirds.AddRange(CircleOfFifths.Notes.Where(n => n.Interval == third));

            if (interval > 4)
                m_fourths.AddRange(CircleOfFifths.Notes.Where(n => n.Interval == fourth - 7));
            else
                m_fourths.AddRange(CircleOfFifths.Notes.Where(n => n.Interval == fourth));

            if (interval > 3)
                m_fifths.AddRange(CircleOfFifths.Notes.Where(n => n.Interval == fifth - 7));
            else
                m_fifths.AddRange(CircleOfFifths.Notes.Where(n => n.Interval == fifth));

            if (interval > 2)
                m_sixths.AddRange(CircleOfFifths.Notes.Where(n => n.Interval == sixth - 7));
            else
                m_sixths.AddRange(CircleOfFifths.Notes.Where(n => n.Interval == sixth));

            if (interval == 1)
                m_sevenths.AddRange(CircleOfFifths.Notes.Where(n => n.Interval == seventh));
            else
                m_sevenths.AddRange(CircleOfFifths.Notes.Where(n => n.Interval == seventh - 7));

            Notes.AddRange(m_roots);
            Notes.AddRange(m_thirds);
            Notes.AddRange(m_fifths);

            //TODO: Figure out how to retrieve token for 7th chords
            switch (CircleOfFifths.CurrentModeName)
            {
                case CircleOfFifths.ModeName.Ionian:
                    NameIonianChord(interval);
                    break;
                case CircleOfFifths.ModeName.Aeolian:
                    NameAolianChord(interval);
                    break;
                case CircleOfFifths.ModeName.MelodicMinor:
                    NameMelodicMinorChord(interval);
                    break;
                case CircleOfFifths.ModeName.HarmonicMinor:
                    NameHarmonicMinorChord(interval);
                    break;
            }
        }

        #endregion

        #region Enums
        #pragma warning disable 1591

        /// <summary>
        /// Scale degree symbols
        /// </summary>
        public enum ScaleDegree
        {
            i,
            I,
            ii,
            iiDim,
            II,
            iii,
            III,
            IIIAug,
            iv,
            IV,
            v,
            V,
            vi,
            VI,
            vii,
            viiDim,
            VII
        }

        /// <summary>
        /// Chord tokens
        /// </summary>
        public enum Token
        {
            M,
            m,
            mAdd2,
            mAdd9,
            MAdd2,
            MAdd9,
            mSus2,
            mSus4,
            MSus2,
            MSus4,
            mSus4Add2,
            Dim,
            Augmented,
            AugmentedAdd11,
            m6,
            M6,
            M6Sus2,
            M6Sus4,
            mb6,
            mb6Sus4,
            Dimb6,
            M7,
            M7Sus2,
            M7Sus4,
            M7no3,
            Dom7,
            Dom7Sus2,
            Dom7Sus4,
            Dom7Sus4Add2,
            m7,
            mM7,
            AugM7,
            Dim7,
            HalfDim7,
            M9,
            M9Sus2,
            m9,
            m7Sus4,
            m7b9,
            m7b5,
            m7b5b9,
            Ninth,
            Dom9,
            Dom9Sus4,
            Sixth,
            Sus2,
            Sus4,
            Add2,
            Add4,
            Add9,
            Sus4Add2,
            Sus4Add4,
            Sus4Add9,
            maj13,
            m13,
            m7b9b13,
            maj13Sharp11,
            Thirteenth,
            m7b13,
            Dim7b9b13,
            Fifth
        }

        #pragma warning restore 1591
        #endregion

        #region Properties

        /// <summary>
        /// Notes comprising this chord
        /// </summary>
        public List<ConcreteNote> Notes = new List<ConcreteNote>();

        /// <summary>
        /// Scale degree of this chord
        /// </summary>
        public ScaleDegree Degree
        {
            get
            {
                return m_degree;
            }
        }

        /// <summary>
        /// Chord name as displayed on the UI
        /// </summary>
        public string DisplayName
        {
            get
            {
                return string.Format("{0}{1} ({2})", Root.Name, m_token, Degree);
            }
        }

        /// <summary>
        /// Root note of this chord
        /// </summary>
        public ConcreteNote Root
        {
            get
            {
                return m_root;
            }
        }

        /// <summary>
        /// Boolean value indicating whether or not the chord has been inverted
        /// </summary>
        public Boolean IsInverted
        {
            get
            {
                return m_isInverted;
            }
        }

        public string Color
        {
            get
            {
                return m_color;
            }
        }

        /// <summary>
        /// Token to be added to the note value when name is displayed
        /// </summary>
        public Token ChordToken
        {
            get
            {
                return m_token;
            }
        }

        /// <summary>
        /// Available root notes to be included across all octaves being employed
        /// </summary>
        public List<ConcreteNote> Roots
        {
            get
            {
                return m_roots;
            }
        }

        /// <summary>
        /// Available second notes to be included across all octaves being employed 
        /// </summary>
        public List<ConcreteNote> Fourths
        {
            get
            {
                return m_fourths;
            }
        }

        /// <summary>
        /// Available thirds across all playing octaves
        /// </summary>
        public List<ConcreteNote> Thirds
        {
            get
            {
                return m_thirds;
            }
        }

        /// <summary>
        /// Available fifths across all playing octaves
        /// </summary>
        public List<ConcreteNote> Fifths
        {
            get
            {
                return m_fifths;
            }
        }

        /// <summary>
        /// Available sixths across all playing octaves
        /// </summary>
        public List<ConcreteNote> Sixths
        {
            get
            {
                return m_sixths;
            }

        }

        /// <summary>
        /// Available fifths across all playing octaves
        /// </summary>
        public List<ConcreteNote> Sevenths
        {
            get
            {
                return m_sevenths;
            }
        }

        /// <summary>
        /// Available ninths across all playing octaves
        /// </summary>
        public List<ConcreteNote> Ninths
        {
            get
            {
                return m_ninths;
            }
        }

        #endregion

        #region Methods

        private void NameIonianChord(int interval)
        {
            if (interval == 1 || interval == 4 || interval == 5)
                    m_token = Token.M;
            else if (interval == 7)
                    m_token = Token.Dim;
            else
                    m_token = Token.m;

            m_degree = m_majorScaleDegrees[interval - 1];
        }

        private void NameAolianChord(int interval)
        {
            if (interval == 1 || interval == 4 || interval == 5)
                m_token = Token.m;
            else if (interval == 2)
                m_token = Token.Dim;
            else
                m_token = Token.M;

            m_degree = m_minorScaleDegrees[interval - 1];
        }

        private void NameMelodicMinorChord(int interval)
        {
            if (interval == 3)
                m_token = Token.Augmented;
            else if (interval == 4 || interval == 5)
                m_token = Token.M;
            else if (interval == 6 || interval == 7)
                m_token = Token.Dim;
            else
                m_token = Token.m;

            m_degree = m_minorScaleDegrees[interval];
        }

        private void NameHarmonicMinorChord(int interval)
        {
            if (interval == 1 || interval == 4)
                m_token = Token.m;
            else if (interval == 2 || interval == 7)
                m_token = Token.Dim;
            else if (interval == 3)
                m_token = Token.Augmented;
            else
                m_token = Token.M;

            m_degree = m_minorScaleDegrees[interval];
        }

        /// <summary>
        /// Returns the interval of a given note
        /// </summary>
        /// <param name="n">The note whose interval is being requested</param>
        /// <returns></returns>
        public string GetChordInterval(ConcreteNote n)
        {
            if (m_roots.Contains(n))
                return "Root";
            else if (m_thirds.Contains(n))
                return "3rd";
            else if (m_fifths.Contains(n))
                return "5th";
            else if (m_sixths.Contains(n))
                return "6th";
            else if (m_sevenths.Contains(n))
                return "7th";
            else if (m_ninths.Contains(n))
                return "9th";
            else
                return null;
        }

        /// <summary>
        /// Add sevenths for this chord
        /// </summary>
        public void AddSevenths()
        {
            if (CircleOfFifths.CurrentModeName == CircleOfFifths.ModeName.Ionian)
            {
                if (m_root.Interval == 0 || m_root.Interval == 3)
                    m_token = Token.M7;
                else if (m_root.Interval == 6)
                    m_token = Token.HalfDim7;
                else if (m_root.Interval == 4)
                    m_token = Token.Dom7;
                else
                    m_token = Token.m7;
            }
            else if (CircleOfFifths.CurrentModeName == CircleOfFifths.ModeName.Aeolian)
            {
                if (m_root.Interval == 6)
                    m_token = Token.Dom7;
                else if (m_root.Interval == 1)
                    m_token = Token.HalfDim7;
                if (m_root.Interval == 2 || m_root.Interval == 5)
                    m_token = Token.M7;
                else
                    m_token = Token.m7;
            }
            else if (CircleOfFifths.CurrentModeName == CircleOfFifths.ModeName.HarmonicMinor)
            {
                switch (m_root.Interval)
                {
                    case 0:
                        m_token = Token.mM7;
                        break;
                    case 1: 
                        m_token = Token.HalfDim7;
                        break;
                    case 2:
                        m_token = Token.AugM7;
                        break;
                    case 3:
                        m_token = Token.m7;;
                        break;
                    case 4:
                        m_token = Token.Dom7;
                        break;
                    case 5:
                        m_token = Token.M7;
                        break;
                    case 6:
                        m_token = Token.Dim7;
                        break;
                 }
            }
            else if (CircleOfFifths.CurrentModeName == CircleOfFifths.ModeName.MelodicMinor)
            {
                if (m_root.Interval == 0)
                    m_token = Token.mM7;
                else if (m_root.Interval == 1)
                    m_token = Token.m7;
                else if (m_root.Interval == 2)
                    m_token = Token.AugM7;
                else if (m_root.Interval == 3 || m_root.Interval == 4)
                    m_token = Token.Dom7;
                else
                    m_token = Token.HalfDim7;
            }

            Notes.AddRange(m_sevenths);
        }

        /// <summary>
        /// Add ninths for this chord
        /// </summary>
        /// <param name="modifier"></param>
        public void AddNinths(string modifier)
        {
            if (modifier.Contains("add"))
                m_token = (Token)System.Enum.Parse(typeof(Token), m_token.ToString() + "Add9");

            else if (CircleOfFifths.CurrentModeName == CircleOfFifths.ModeName.Ionian)
            {
                if (m_root.Interval == 0 || m_root.Interval == 3)
                    m_token = Token.M9;
                else if (m_root.Interval == 6)
                    m_token = Token.m7b5b9;
                else if (m_root.Interval == 4)
                    m_token = Token.Dom9;
                else if (m_root.Interval == 2)
                    m_token = Token.m7b9;
                else
                    m_token = Token.m9;
            }
            else
            {
                if (m_root.Interval == 0 || m_root.Interval == 3)
                    m_token = Token.m9;
                else if (m_root.Interval == 2 || m_root.Interval == 5)
                    m_token = Token.M9;
                else if (m_root.Interval == 6)
                    m_token = Token.Dom9;
                else
                    throw new Exception("No implementation for the requested chord.");
            }

            Notes.AddRange(m_ninths);

            if (!modifier.Contains("add"))
                Notes.AddRange(m_sevenths);
        }

        /// <summary>
        /// Add 13ths for this chord
        /// </summary>
        /// <param name="modifier"></param>
        public void AddSixths(string modifier)
        {
            bool isSixth = modifier.Contains("6");

            //Note - we are using the full chord names although voicings may not include 9ths & 11ths
            if (CircleOfFifths.CurrentModeName == CircleOfFifths.ModeName.Ionian)
            {
                if (isSixth)
                {
                    switch (m_root.Interval)
                    {
                        case 0:
                            m_token = Token.M6;
                            break;
                        case 1:
                            m_token = Token.m6;
                            break;
                        case 2:
                            m_token = Token.mb6;
                            break;
                        case 3:
                            m_token = Token.M6;
                            break;
                        case 4:
                            m_token = Token.M6;
                            break;
                        case 5:
                            m_token = Token.mb6;
                            break;
                        case 6:
                            m_token = Token.Dimb6;
                            break;
                    }
                }
                else
                {
                    switch (m_root.Interval)
                    {
                        case 0:
                            m_token = Token.maj13;
                            break;
                        case 1:
                            m_token = Token.m13;
                            break;
                        case 2:
                            m_token = Token.m7b9b13;
                            break;
                        case 3:
                            m_token = Token.maj13Sharp11;
                            break;
                        case 4:
                            m_token = Token.Thirteenth;
                            break;
                        case 5:
                            m_token = Token.m7b13;
                            break;
                        case 6:
                            m_token = Token.Dim7b9b13;
                            break;
                    }
                }
            }
            else
            {
                if (isSixth)
                {
                    switch (m_root.Interval)
                    {
                        case 0:
                            m_token = Token.mb6;
                            break;
                        case 1:
                            m_token = Token.Dimb6;
                            break;
                        case 2:
                            m_token = Token.M6;
                            break;
                        case 3:
                            m_token = Token.m6;
                            break;
                        case 4:
                            m_token = Token.mb6;
                            break;
                        case 5:
                            m_token = Token.M6;
                            break;
                        case 6:
                            m_token = Token.M6;
                            break;
                    }
                }
                else
                {
                    switch (m_root.Interval)
                    {
                        case 0:
                            m_token = Token.m7b13;
                            break;
                        case 1:
                            m_token = Token.Dim7b9b13;
                            break;
                        case 2:
                            m_token = Token.maj13;
                            break;
                        case 3:
                            m_token = Token.m13;
                            break;
                        case 4:
                            m_token = Token.m7b9b13;
                            break;
                        case 5:
                            m_token = Token.maj13Sharp11;
                            break;
                        case 6:
                            m_token = Token.Thirteenth;
                            break;
                    }
                }
            }

            Notes.AddRange(m_sixths);
        }

        /// <summary>
        /// Play a 4th chord
        /// </summary>
        public void AddFourths(bool suspended = false)
        {
            Notes.AddRange(m_fourths);

            if (suspended == true)
            {
                //This isn't necessary since thirds notes are omitted when suspended chords are played by the RhythmPhrase class
                //Notes.RemoveAll(n => m_thirds.Contains(n));
                m_token = (Token)System.Enum.Parse(typeof(Token), m_token.ToString() + "Sus4");
            }
            else
                m_token = (Token)System.Enum.Parse(typeof(Token), m_token.ToString() + "Add4");
        }

        /// <summary>
        /// Add a 2nd
        /// </summary>
        public void AddSeconds(bool suspended = false)
        {
            Notes.AddRange(m_ninths);

            if (suspended == true)
            {
                //This isn't necessary since thirds notes are omitted when suspended chords are played by the RhythmPhrase class
                //Notes.RemoveAll(n => m_thirds.Contains(n));
                m_token = (Token)System.Enum.Parse(typeof(Token), m_token.ToString() + "Sus2");
            }
            else
                m_token = (Token)System.Enum.Parse(typeof(Token), m_token.ToString() + "Add2");
        }

        /// <summary>
        /// Just set the token for "no" chords
        /// </summary>
        /// <param name="omitted"></param>
        public void Remove(string omitted)
        {
            if (omitted == "Power")
                m_token = Token.Fifth;
            else
                m_token = (Token)System.Enum.Parse(typeof(Token), m_token.ToString() + "no" + omitted);
        }

        /// <summary>
        /// Return clone of this chord
        /// </summary>
        /// <returns></returns>
        public object Clone()
        {
            return this.MemberwiseClone();
        }

        #endregion

    }
}
